package at.fhooe.stefanirndorfer.sound;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;

import org.lwjgl.LWJGLException;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.AL11;
import org.lwjgl.openal.ALC10;
import org.lwjgl.openal.EFX10;

import at.fhooe.stefanirndorfer.logging.Log;

public class OpenALUtil {
	
	/** Speed of Sound in m/s */
	public static final double SOS = 340.29;
	public static int ONE_METER_IN_PX = 50;
	public static double SCAN_RESOLUTION = 1.0;
	
	public static final float HF_ABSORBTION_PER_METER = 0.01f;
	public static final float LF_ABSORBTION_PER_METER = 0.001f;
	
	
	
	public static void setOneMeterInPx(int px){
		ONE_METER_IN_PX = px;
	}
	
	
	/**
	 * Creates an integer buffer to hold specified ints - strictly a utility
	 * method.
	 * 
	 * @param size
	 *            how many int to contain
	 * @return created IntBuffer
	 */
	protected static IntBuffer createIntBuffer(int size) {
		ByteBuffer temp = ByteBuffer.allocateDirect(4 * size);
		temp.order(ByteOrder.nativeOrder());

		return temp.asIntBuffer();
	}
	
	
	/**
	 * creates an openAL object. to be used by the soundmanager
	 */
	public static void createOpenAL() {
		try {
			AL.create();
		} catch (Exception e) {
			Log.info(
					"OpenALUtil",
					"Unable to create OpenAL.\nPlease make sure that OpenAL is available on this system. Exception: "
							+ e);
			return;
		}

		System.out.println("Default device: "
				+ ALC10.alcGetString(null, ALC10.ALC_DEFAULT_DEVICE_SPECIFIER));

		if (ALC10.alcIsExtensionPresent(null, "ALC_ENUMERATION_EXT")) {
			String[] devices = ALC10.alcGetString(null,
					ALC10.ALC_DEVICE_SPECIFIER).split("\0");
			System.out.println("Available devices: ");
			for (int i = 0; i < devices.length; i++) {
				System.out.println(i + ": " + devices[i]);
			}
		}

		Log.info("OpenAL Info", "AL_VERSION = " + AL10.alGetString(AL10.AL_VERSION));
		Log.info("OpenAL Info", "AL_RENDERER = " +AL10.alGetString(AL10.AL_RENDERER));
		Log.info("OpenAL Info", "AL_VENDOR = " + AL10.alGetString(AL10.AL_VENDOR));
		Log.info("OpenAL Info", "AL_EXTENSIONS = " + AL10.alGetString(AL10.AL_EXTENSIONS));
		Log.info("OpenAL Info", "AL_CHANNELS = " + AL10.alGetListeneri(AL10.AL_CHANNELS));
		
		checkError();
		
//		 // Load and create OpenAL
//        if (!AL.isCreated()) {
//            try {
//				AL.create();
//			} catch (LWJGLException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
//        }
//        // Query for Effect Extension
//        if (!ALC10.alcIsExtensionPresent(AL.getDevice(), EFX10.ALC_EXT_EFX_NAME)) {
//            try {
//				throw new Exception("No ALC_EXT_EFX supported by driver.");
//			} catch (Exception e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
//        }
//        System.out.println("ALC_EXT_EFX found.");
		
	   
	}
	
	
	
	
	/**
	 * 
	 * @return
	 */
	public static int checkError(){
	    int lastError;
	    if((lastError = AL10.alGetError()) != AL10.AL_NO_ERROR){
	      Log.info("OpenAL", "OpenAL Error: " + lastError);
	      return lastError;
	    }else{
	    	return AL10.AL_NO_ERROR;
	    }
	    
	  }

	/**
	 * 
	 * @param soundMap
	 * @param sources
	 */
	public static void cleanUp(HashMap<String, OpenALWave> soundMap,
			OpenALSource sourcePool) {
		
		deleteAllBuffers(soundMap);
		//deleteAllSources(sources);
		OpenALSource.alDeleteSources();
		AL.destroy();
		
	}
	
	
//	private static void deleteAllSources(List<SoundPlayer> sources) {
//		while(sources.size() > 0){
//			AL10.alDeleteSources(sources.get(0).getSourceBuffer());
//			sources.remove(0);
//		}
//		
//	}

	private static void deleteAllBuffers(HashMap<String, OpenALWave> soundMap) {
		for(Object value : soundMap.values()){
				((OpenALWave) value).deleteBuffer();
		}
	}


	/**
	 * Calculated in 2D in first place
	 * @param sourcePos
	 * @param listenerPosition
	 */
	public static double calcEuclDist(FloatBuffer sourcePos,
			float[] listenerPosition) {
		double distX = sourcePos.get(0) - listenerPosition[0];
		double distY = sourcePos.get(1) - listenerPosition[1];
		return Math.sqrt(distX * distX + distY * distY);
	}


	public static double calcEuclDist(double[] position, FloatBuffer sourcePos) {
		float[] array = doubleArrayToFloat(position);
		return calcEuclDist(sourcePos, array);
	}


	private static float[] doubleArrayToFloat(double[] position) {
		float[] array = new float[3];
		array[0] = (float)position[0];
		array[1] = (float)position[1];
		return array;
	}


	public static double calcEuclDist(double[] position,
			float[] listenerPosition) {
		float[] pos = doubleArrayToFloat(position);
		
		return calcEuclDist(pos, listenerPosition);
	}


	private static double calcEuclDist(float[] a, float[] b) {
		double distX = a[0] - b[0];
		double distY = a[1] - b[1];
		return Math.sqrt(distX * distX + distY * distY);
	}


	/**
	 * Calculates the delaytime based on:
	 * @param totalDist
	 */
	public static int calcDelayFromDistance(double totalDist) {
		return (int)(totalDist/ONE_METER_IN_PX / SOS * 1000);
	}


	/**
	 *  
	 * @param totalDist
	 * @return a double[] containing on 0 low frequency absorbtion 
	 * and on 1 high frequency absorbtion
	 */
	public static double[] calculateAirAbsorbtion(double totalDist) {
		double hf = totalDist/ONE_METER_IN_PX * HF_ABSORBTION_PER_METER;
		double lf = totalDist/ONE_METER_IN_PX * LF_ABSORBTION_PER_METER;
		
		return new double[]{lf, hf};
	}
	
}
